package com.zkh.myutils.net.ftp;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.zkh.myutils.io.IOUtils;
import com.zkh.myutils.io.log.Logger;
import com.zkh.myutils.net.SocketClient;
import com.zkh.myutils.net.SocketLogListener;
import com.zkh.myutils.net.utils.NetUtils;
import com.zkh.myutils.net.utils.Reply;
import com.zkh.myutils.utils.StringUtils;
import com.zkh.myutils.utils.Utils;
import com.zkh.myutils.utils.UtilsException;

/**
 * FTP客户端
 * @author zkh
 */
public class FTP extends SocketClient {
	//默认数据端口
	public static final int DEFAULT_DATA_PORT = 20;
	//默认命令端口
	public static final int DEFAULT_PORT = 21;
	
	/**
	 * 数据连接模式之主动模式（本地客户端到服务端）
	 */
	public static final int DATA_CONNECTION_MODE_ACTIVE_LOCAL = 0;
	/**
	 * 数据连接模式之主动模式（另一个服务端到服务端）
	 */
	public static final int DATA_CONNECTION_MODE_ACTIVE_REMOTE = 1;
	/**
	 * 数据连接模式之被动模式（服务端到本地客户端）
	 */
	public static final int DATA_CONNECTION_MODE_PASSIVE_LOCAL = 2;
	/**
	 * 数据连接模式之被动模式（服务端到另一个服务端）
	 */
	public static final int DATA_CONNECTION_MODE_PASSIVE_REMOTE = 3;
	
	//数据连接模式
	private int dataConnectionMode = DATA_CONNECTION_MODE_ACTIVE_LOCAL;
	
	//被动模式响应正则
	private static final Pattern PASV_RESP_PATTERN = Pattern.compile("(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})");
	//一系列变量
	private InetAddress activeExternalHost;	//主动模式主机（IPV6）
	private InetAddress passiveLocalHost;
	private InetAddress reportActiveExternalHost;	//主动模式下服务器要发送的目标地址（本地地址或另一台服务器）
	private int activeMinPort;	//主动模式开启端口（最小端口）
	private int activeMaxPort;	//主动模式开启端口（最大端口）
	private long restartOffset;
	private int dataTimeout = -1;	//数据传输的超时时间
	private int bufferSize;		//上传下载时，复制流的缓冲大小
	private int sendDataSocketBufferSize;	//发送数据的缓存大小（数据连接）
	private int receiveDataSocketBufferSize;	//接收数据的缓存大小（数据连接）
	private boolean useEPSVwithIPv4;	//是否尝试EPSV和v4混合使用
	private int passivePort;	//被动模式端口
	private String passiveHost;	//被动模式主机
	private boolean passiveNatWorkaround = true;	//启用或禁用被动模式下的内部IP替换
	
	{
		//初始化监听器
		initCommandEventListenerHandler(this);
		//如果是调试模式，添加数据传输监听器
		if(Logger.isDebug()) {
			getTransferEventListenerHandler().addTransferEventListener(new SocketLogListener());
		}
	}

	/**
	 * 创建连接
	 */
	@Override
	protected void connect() throws IOException{
		//设置了连接超时
		if (connectTimeout > 0) {
			//设置并保存原配置
			int original = socket.getSoTimeout();
			socket.setSoTimeout(connectTimeout);
			try {
				getReply();
				//如果代码为120，必须要获取完整响应
				if (Reply.isPositivePreliminary(replyCode)) {
					getReply();
				}
			} catch (SocketTimeoutException e) {
				IOException ioe = new IOException("初始化超时");
				ioe.initCause(e);
				throw ioe;
			} finally {//设置失败，还原原有配置
				socket.setSoTimeout(original);
			}
		} else {
			//处理响应
        	getReply();
        	//如果代码为120，必须要获取完整响应
            if (Reply.isPositivePreliminary(replyCode)) {
            	getReply();
            }
        }
	}/**
	 * 设置主动模式开启端口的范围。取决于客户端开放的端口。
	 * @param minPort 最小端口，一般大于1024
	 * @param maxPort 最大端口
	 */
	public void setActivePortRange(int minPort, int maxPort) {
		this.activeMinPort = minPort;
		this.activeMaxPort = maxPort;
	}
	/**
	 * 将外部IP地址设置为活动模式
	 */
	public void setActiveExternalIPAddress(String ipAddress) throws UnknownHostException{
		this.activeExternalHost = InetAddress.getByName(ipAddress);
	}
	public void setPassiveLocalIPAddress(String ipAddress) throws UnknownHostException{
		this.passiveLocalHost = InetAddress.getByName(ipAddress);
	}
	public void setPassiveLocalIPAddress(InetAddress inetAddress) {
		this.passiveLocalHost = inetAddress;
	}
	public void setReportActiveExternalIPAddress(String ipAddress) throws UnknownHostException{
		this.reportActiveExternalHost = InetAddress.getByName(ipAddress);
	}
	/**
	 * 获取一个开放端口
	 */
	private int getActivePort() {
		if (activeMinPort > 0 && activeMaxPort >= activeMinPort) {
			return activeMinPort==activeMaxPort ? activeMinPort : Utils.rand(activeMinPort, activeMaxPort + 1);
		}else {
			return 0;
		}
	}
	/**
	 * 获取本地主机地址（如果有配置IPv6，就使用IPv6）
	 */
	private InetAddress getHostAddress() {
		return activeExternalHost!=null ? activeExternalHost : getLocalAddress();
	}
	/**
	 * 获取主动模式下服务器要发送数据的地址（如果有配置另一台服务器，就使用，否则就取本地地址）
	 */
	private InetAddress getReportHostAddress() {
		return reportActiveExternalHost!=null ? reportActiveExternalHost : getHostAddress();
	}
	public void setDataTimeout(int timeout){
		this.dataTimeout = timeout;
	}
	public void setBufferSize(int bufSize) {
		bufferSize = bufSize;
	}
	public int getBufferSize() {
		return bufferSize;
	}
	public void setSendDataSocketBufferSize(int bufSize) {
		sendDataSocketBufferSize = bufSize;
	}
	public int getSendDataSocketBufferSize() {
		return sendDataSocketBufferSize;
	}
	public void setReceieveDataSocketBufferSize(int bufSize) {
		receiveDataSocketBufferSize = bufSize;
	}
	public int getReceiveDataSocketBufferSize() {
		return receiveDataSocketBufferSize;
	}
	public boolean isUseEPSVwithIPv4() {
		return useEPSVwithIPv4;
	}
	public void setUseEPSVwithIPv4(boolean useEPSVwithIPv4) {
		this.useEPSVwithIPv4 = useEPSVwithIPv4;
	}
	
	/**
	 * 发送主动模式信息（地址+端口）到服务器、接收回复并返回回复代码。如：<br>
	 *  <li>EPRT |1|132.235.1.2|6275|</li>
	 *  <li>EPRT |2|1080::8:800:200C:417A|5282|</li>
	 * @param host 主机
	 * @param port 本地开启的端口
	 */
	public int eprt(InetAddress host, int port) throws IOException{
		String hosts = host.getHostAddress();
		int num = hosts.indexOf('%');
		if (num > 0) {
			hosts = hosts.substring(0, num);
		}
		//参数
		String arg = "|" + (host instanceof Inet4Address ? "1" : "2") + "|" + hosts + "|" + port + "|";
		//发送消息
		return sendCommand(FTPCmd.ACTIVE_MODE_EXTEND, arg);
	}
	
	/**
	 * 发送FTP PORT命令到服务器、接收回复并返回回复代码。
	 */
	public int port(InetAddress host, int port) throws IOException{
        //参数
        String args = host.getHostAddress().replace('.', ',') + "," + (port >>> 8) + "," + (port & 0xff);
        //发送命令
        return sendCommand(FTPCmd.ACTIVE_MODE, args);
	}
	
	/**
	 * 登录
	 * @param username 用户名
	 * @param password 密码
	 */
	public boolean login(String username, String password) throws IOException {
		return login(username, password, null);
	}
	
	/**
	 * 登录
	 * @param username 用户名
	 * @param password 密码
	 * @param account 帐号
	 */
	public boolean login(String username, String password, String account) throws IOException {
		//发送用户命令
		sendCommand(FTPCmd.USERNAME, username);
		//请求完成
		if (Reply.isPositiveCompletion(replyCode)) {
			return true;
		}
		if (!Reply.isPositiveIntermediate(replyCode)) {
			return false;
		}
		//发送密码命令
		sendCommand(FTPCmd.PASSWORD, password);
		//请求完成
		if (Reply.isPositiveCompletion(replyCode)) {
			return true;
		}
		//若帐号为空，只需要验证到密码即可
		if(account==null) {
			return false;
		}
		if (!Reply.isPositiveIntermediate(replyCode)) {
			return false;
		}
		//验证密码
		return Reply.isPositiveCompletion(sendCommand(FTPCmd.ACCOUNT, account));
	}
	
	/**
	 * 退出
	 */
	public boolean logout() throws IOException{
		return Reply.isPositiveCompletion(sendCommand(FTPCmd.LOGOUT));
	}
	
	/**
	 * 切换工作目录
	 * @param pathname 目录
	 */
	public boolean changeWorkingDirectory(String pathname) throws IOException{
		return Reply.isPositiveCompletion(sendCommand(FTPCmd.CHANGE_WORKING_DIRECTORY, pathname));
	}
	
	/**
	 * 回到根目录
	 */
	public void changeToRootDirectory() throws IOException{
		changeWorkingDirectory("/");
	}
	
	/**
	 * 切换上级目录
	 */
	public boolean changeToParentDirectory() throws IOException{
		return Reply.isPositiveCompletion(sendCommand(FTPCmd.CHANGE_TO_PARENT_DIRECTORY));
	}
	
	/**
	 * 改变目录，目录不存在时抛出异常
	 */
	public boolean changeDirectoryThrows(String pathname) throws IOException{
		return changeWorkingDirectory(pathname, false);
	}
	
	/**
	 * 改变目录，目录不存在时创建目录
	 */
	public boolean changeDirectoryMake(String pathname) throws IOException{
		return changeWorkingDirectory(pathname, true);
	}

	/**
	 * 创建多级目录，指针始终在最新创建的目录下。
	 * @param filepath 目录名称
	 */
	public void makeDirectorys(String filepath) {
		try{
			//替换斜线
			filepath = filepath.replaceAll("\\\\", "/");
			//开始
			if(filepath.startsWith("/")){
				//根目录开始遍历
				this.changeToRootDirectory();
				filepath = filepath.substring(1);
			}
			//分割目录
			String[] paths = filepath.split("/");
			//遍历创建目录
			for(String path: paths){
				//创建目录
				this.makeDirectory(path);
				//进入目录
				this.changeWorkingDirectory(path);
			}
		}catch(IOException e){
			//抛出异常
			throw new UtilsException(e);
		}
	}
	
	/**
	 * 创建一级目录，指针不进入。
	 * @param pathname 目录名称
	 */
	public boolean makeDirectory(String pathname) throws IOException {
		return Reply.isPositiveCompletion(sendCommand(FTPCmd.MAKE_DIRECTORY, pathname));
	}
	
	/**
	 * 以指定文件名保存文件流，如果文件已存在，则直接覆盖
	 * @param remote FTP上的文件名
	 * @param local 要上传的本地文件流
	 */
	public boolean storeFile(String remote, InputStream local) throws IOException{
		return storeFile(remote, local, false);
	}
	
	/**
	 * 以指定文件名保存文件流
	 * @param remote FTP上的文件名
	 * @param local 要上传的本地文件流
	 * @param append 是否追加。为true时，如果文件已存在，则追加，而非覆盖文件
	 */
	public boolean storeFile(String remote, InputStream local, boolean append) throws IOException{
		return storeFile(remote, local, append, false);
	}
	
	/**
	 * 以随机且唯一的文件名保存文件流
	 * @param local 要上传的本地文件流
	 */
	public boolean storeUniqueFile(InputStream local) throws IOException{
		return storeUniqueFile(null, local);
	}
	
	/**
	 * 以随机且唯一的文件名保存文件流
	 * @param remote 指定唯一文件名的前缀
	 * @param local 要上传的本地文件流
	 */
	public boolean storeUniqueFile(String remote, InputStream local) throws IOException{
		return storeFile(null, local, false, true);
	}
	
	/**
	 * 保存文件流
	 * @param remote FTP上的文件名
	 * @param local 要上传的本地文件流
	 * @param append 是否追加。为true时，如果文件已存在，则追加，而非覆盖文件
	 * @param unique 使用唯一名称。当该项为true时，append参数失效
	 */
	private boolean storeFile(String remote, InputStream local, boolean append, boolean unique) throws IOException{
		//命令
		String command = unique ? FTPCmd.STORE_UNIQUE : (append ? FTPCmd.APPEND : FTPCmd.STORE);
		//创建数据连接
		Socket socket = openDataConnection(command, remote);
		//为空
		if(socket==null) {
			return false;
		}
		//获取输出流
		OutputStream output = getBufferedOutputStream(socket.getOutputStream());
		//处理流
		try {
			NetUtils.copyStream(local, output, bufferSize);
		}catch (IOException e) {
			IOUtils.closeQuietly(socket);
			throw e;
		}
		output.close();
		socket.close();
		//完成
		return completePendingCommand();
	}
	
	/**
	 * 从另一台服务器保存到服务器
	 * @param filename 文件名
	 */
	public boolean remoteStore(String filename) throws IOException{
		if(dataConnectionMode==DATA_CONNECTION_MODE_ACTIVE_REMOTE || dataConnectionMode==DATA_CONNECTION_MODE_PASSIVE_REMOTE) {
			return Reply.isPositivePreliminary(sendCommand(FTPCmd.STORE, filename));
		}
		return false;
	}
	
	/**
	 * 从另一台服务器保存到服务器，文件名随机且唯一
	 */
	public boolean remoteStoreUnique() throws IOException{
		if(dataConnectionMode==DATA_CONNECTION_MODE_ACTIVE_REMOTE || dataConnectionMode==DATA_CONNECTION_MODE_PASSIVE_REMOTE) {
			return Reply.isPositivePreliminary(sendCommand(FTPCmd.STORE_UNIQUE));
		}
		return false;
	}
	
	/**
	 * 从另一台服务器保存到服务器，文件名随机且唯一但指定前缀
	 * @param filename 指定文件名前缀
	 */
	public boolean remoteStoreUnique(String filename) throws IOException{
		if(dataConnectionMode==DATA_CONNECTION_MODE_ACTIVE_REMOTE || dataConnectionMode==DATA_CONNECTION_MODE_PASSIVE_REMOTE) {
			return Reply.isPositivePreliminary(sendCommand(FTPCmd.STORE_UNIQUE, filename));
		}
		return false;
	}
	
	/**
	 * 列举出目录下的所有文件
	 */
	public FTPFile[] listFiles() throws IOException{
		return listFiles(null);
	}
	
	/**
	 * 获取所有名称
	 */
	public String[] listNames() throws IOException{
		return listNames(null);
	}
	
	/**
	 * 获取所有名称
	 * @param pathname 指定目录
	 */
	public String[] listNames(String pathname) throws IOException{
		//获取数据
		List<String> list = listFileName(FTPCmd.NAME_LIST, pathname, e->e);
		//返回最终值
		return list.toArray(new String[list.size()]);
	}
	
	/**
	 * 列举出目录下的所有文件（指针不变）
	 * @param pathname 指定目录
	 */
	public FTPFile[] listFiles(String pathname) throws IOException{
		//获取数据
		List<FTPFile> list = listFileName(FTPCmd.LIST, pathname, e->new FTPFile(e));
		//返回最终值
		return list.toArray(new FTPFile[list.size()]);
	}
	
	/**
	 * 获取文件（名）列表（指针不变）
	 * @param command 执行命令
	 * @param pathname 文件目录
	 * @param fun 转换操作
	 */
	private <T> List<T> listFileName(String command, String pathname, Function<String, T> fun) throws IOException{
		//打开连接
		Socket socket = openDataConnection(command, StringUtils.isEmpty(pathname) ? "-a" : "-a " + pathname);
		//空
		if(socket==null) {
			return new ArrayList<>();
		}
		//读数据句柄
		InputStreamReader isr = new InputStreamReader(socket.getInputStream(), getCharset());
		BufferedReader br = new BufferedReader(isr);
		//容器
		List<T> ctn = new ArrayList<>();
		String line;
		//读取数据
		while((line=br.readLine())!=null) {
			//跳过.和..
			if(!line.endsWith(".") && !line.endsWith("..")) {
				ctn.add(fun.apply(line));
			}
		}
		//关闭流
		br.close();
		isr.close();
		socket.close();
		//结束
		completePendingCommand();
		//返回最终值
		return ctn;
	}
	
	/**
	 * 下载文件
	 * @param remote 服务器上的文件
	 * @param local 本地保存流
	 */
	public boolean retrieveFile(String remote, OutputStream local) throws IOException{
		//打开连接
		Socket socket = openDataConnection(FTPCmd.RETRIEVE, remote);
		//空
		if(socket==null) {
			return false;
		}
		//获取流
		InputStream input = getBufferedInputStream(socket.getInputStream());
		//处理流
		try {
			NetUtils.copyStream(input, local, bufferSize);
		}finally {
			IOUtils.closeQuietly(socket);
			IOUtils.closeQuietly(input);
		}
		//完成状态
		return completePendingCommand();
	}
	
	/**
	 * 从服务器下载到另一台服务器
	 * @param filename 下载的文件名
	 */
	public boolean remoteRetrieve(String filename) throws IOException{
		if(dataConnectionMode==DATA_CONNECTION_MODE_ACTIVE_REMOTE || dataConnectionMode==DATA_CONNECTION_MODE_PASSIVE_REMOTE) {
			return Reply.isPositivePreliminary(sendCommand(FTPCmd.RETRIEVE, filename));
		}
		return false;
	}
	
	/**
	 * 获取数据连接模式
	 */
	public int getDataConnectionMode() {
		return dataConnectionMode;
	}
	
	/**
	 * 为服务器上的文件存储器分配字节
	 */
	public boolean allocate(int bytes) throws IOException{
		return Reply.isPositiveCompletion(sendCommand(FTPCmd.ALLOCATE, Integer.toString(bytes)));
	}
	
	/**
	 * 重启文件传递
	 * @param offset 偏移量（从指定偏移量开始，计为0）
	 */
	protected boolean restart(long offset) throws IOException{
		//重置偏移量
		restartOffset = 0;
		//发送命令
		return Reply.isPositiveIntermediate(sendCommand(FTPCmd.RESTART, Long.toString(offset)));
	}
	public void setRestartOffset(long offset) {
		if(offset >= 0) {
			restartOffset = offset;
		}
	}
	public long getRestartOffset() {
		return restartOffset;
	}
	
	/**
	 * 发出FTP SMNT命令
	 */
	public boolean structureMount(String pathname) throws IOException{
		return Reply.isPositiveCompletion(sendCommand(FTPCmd.STRUCTURE_MOUNT, pathname));
	}
	
	/**
	 * 重新初始化
	 */
	boolean reinitialize() throws IOException{
		sendCommand(FTPCmd.REINITIALIZE);
		if (Reply.isPositiveCompletion(replyCode) ||
				(Reply.isPositivePreliminary(replyCode) && Reply.isPositiveCompletion(getReply()))){
			return true;
        }
		return false;
	}
	
	/**
	 * 判断是否存在指定路径的目录或文件<br />
	 * <b>注：<br />
	 * 1、判断绝对路径(以“/”开始)后，指针统一回到根目录。非绝对路径则指针不变。<br />
	 * 2、相对路径不支持对包含“../”路径的判断，将统一返回false。</b>
	 * 
	 * @param name 目录或文件名
	 */
	public boolean exist(String name) throws IOException{
		return checkFile(name, 0);
	}
	
	/**
	 * 执行命令
	 * @param command 命令
	 * @param params 参数
	 */
	public boolean doCommand(String command, String params) throws IOException{
		return Reply.isPositiveCompletion(sendCommand(command, params));
	}
	
	/**
	 * 执行命令，返回响应字符串
	 * @param command 命令
	 * @param params 参数
	 */
	public String[] doCommandAsStrings(String command, String params) throws IOException{
		boolean success = Reply.isPositiveCompletion(sendCommand(command, params));
		if (success){
			return replyLines.toArray(new String[replyLines.size()]);
		} else {
			return null;
		}
	}
	
	/**
	 * 重命名
	 * @param from 原文件名
	 * @param to 新文件名
	 */
	public boolean rename(String from, String to) throws IOException{
		if (!Reply.isPositiveIntermediate(sendCommand(FTPCmd.RENAME_FROM, from))) {
			return false;
		}
		return Reply.isPositiveCompletion(sendCommand(FTPCmd.RENAME_TO, to));
	}
	
	/**
	 * 中断执行
	 */
	public boolean abort() throws IOException{
		return Reply.isPositiveCompletion(sendCommand(FTPCmd.ABORT));
	}
	
	/**
	 * 删除文件
	 * @param pathname 要删除的文件
	 */
	public boolean deleteFile(String pathname) throws IOException{
		return Reply.isPositiveCompletion(sendCommand(FTPCmd.DELETE_FILE, pathname));
	}
	
	/**
	 * 删除目录
	 * @param pathname 指定目录
	 */
	public boolean removeDirectory(String pathname) throws IOException{
		return Reply.isPositiveCompletion(sendCommand(FTPCmd.DELETE_DIRECTORY, pathname));
	}
	
	/**
	 * 打印当前目录
	 */
	public String printWorkingDirectory() throws IOException{
		if (sendCommand(FTPCmd.PRINT_WORKING_DIRECTORY) != FTPReply.PATHNAME_CREATED) {
			return null;
		}
		//获取输出
		String str = replyLines.get(replyLines.size() - 1);
		//截取
		return str.substring(5, str.indexOf('"', 5));
	}
	
	/**
	 * 发送SITE命令
	 */
	public boolean sendSiteCommand(String arguments) throws IOException{
		return Reply.isPositiveCompletion(sendCommand(FTPCmd.SITE_PARAMETERS, arguments));
	}
	
	/**
	 * 帮助命令
	 */
	public String listHelp() throws IOException{
		if (Reply.isPositiveCompletion(sendCommand(FTPCmd.HELP))) {
			return getReplyString();
		}
		return null;
	}
	
	/**
	 * 查看指定命令的帮助文档
	 */
	public String listHelp(String command) throws IOException{
		if (Reply.isPositiveCompletion(sendCommand(FTPCmd.HELP, command))) {
			return getReplyString();
		}
		return null;
	}
	
	/**
	 * 发送无操作命令
	 */
	public boolean sendNoOp() throws IOException{
		return Reply.isPositiveCompletion(sendCommand(FTPCmd.NO_OPERATE));
	}
	
	/**
	 * 获取状态
	 */
	public String getStatus() throws IOException{
		if(Reply.isPositiveCompletion(sendCommand(FTPCmd.STATUS))){
			return getReplyString();
		}
		return null;
	}
	
	/**
	 * 获取修改时间
	 */
	public String getModificationTime(String pathname) throws IOException{
		if (Reply.isPositiveCompletion(sendCommand(FTPCmd.MODIFY_TIME, pathname))) {
			return getReplyString();
		}
		return null;
	}
	
	/**
	 * 检查指定路径是否是文件<br />
	 * <b>注：<br />
	 * 1、判断绝对路径(以“/”开始)后，指针统一回到根目录。非绝对路径则指针不变。<br />
	 * 2、相对路径不支持对包含“../”路径的判断，将统一返回false。</b>
	 */
	public boolean isFile(String filename) throws IOException{
		return checkFile(filename, 1);
	}
	
	/**
	 * 检查指定路径是否是目录<br />
	 * <b>注：<br />
	 * 1、判断绝对路径(以“/”开始)后，指针统一回到根目录。非绝对路径则指针不变。<br />
	 * 2、相对路径不支持对包含“../”路径的判断，将统一返回false。</b>
	 * 
	 * @param path 目录名
	 */
	public boolean isDirectory(String path) throws IOException {
		return checkFile(path, 2);
	}
	
	/**
	 * 检查文件，供内部调用
	 * @param name 名称
	 * @param type 检查类型。0：是否存在，1：是否是文件，2：是否是目录
	 */
	private boolean checkFile(String filepath, int type) throws IOException{
		//为空
		if(StringUtils.isEmpty(filepath)){
			return false;
		}
		//替换斜线
		filepath = filepath.replaceAll("\\\\", "/");
		//如果以“/”开头
		if(filepath.startsWith("/")){
			//根目录开始遍历
			this.changeToRootDirectory();
			filepath = filepath.substring(1);
		}
		//当前层级
		int level = 0;
		//分割目录
		String[] paths = filepath.split("/");
		//目录长度
		int len = paths.length;
		//默认存在
		boolean exist = true;
		//遍历
		for(int i=0;i<len;i++){
			//当前目录或文件
			String path = paths[i];
			//遍历所有目录，查看是否存在
			FTPFile[] file = this.listFiles();
			//默认不存在
			boolean flag = false;
			//是否是最后一级
			boolean last = i==len-1;
			//遍历
			for(FTPFile f: file){
				//是否查找到文件
				if(path.equals(f.getName())){
					//不是最后一级（肯定是目录）
					if(!last){
						this.changeWorkingDirectory(f.getName());
						level++;	//层级加1
						flag = true;
						//不再判断
						break;
					}else{
						flag = (type==0) || (type==1 && f.isFile()) || (type==2 && f.isDirectory());
					}
				}
			}
			//如果不存在，则直接退出
			if(!flag){
				exist = false;	//标记不存在
				break;
			}
		}
		//回退层级
		while(level>0){
			this.changeToParentDirectory();
			level--;
		}
		//返回状态
		return exist;
	}
	
	/**
	 * 打开数据连接
	 * @param command 命令
	 */
	protected Socket openDataConnection(String command, String arg) throws IOException {
		//检查数据连接模式（屏蔽服务端到另一个服务端的连接）
		if(dataConnectionMode!=DATA_CONNECTION_MODE_ACTIVE_LOCAL && dataConnectionMode!=DATA_CONNECTION_MODE_PASSIVE_LOCAL) {
			return null;
		}
		//服务端ip版本是否是v6
		final boolean ipv6 = getRemoteAddress() instanceof Inet6Address;
		Socket socket;
		//主动模式（要在本地开启端口，作为临时服务器，所以创建的是ServerSocket）
		if(dataConnectionMode==DATA_CONNECTION_MODE_ACTIVE_LOCAL) {
			//创建一个服务端Socket（端口在设置范围内任选一个）
			ServerSocket server = serverSocketFactory.createServerSocket(getActivePort(), 1, getHostAddress());
			//响应代码
			int replyCode = 0;
			try {
				//发送主动命令
				replyCode = ipv6 ? eprt(getReportHostAddress(), server.getLocalPort()) : port(getReportHostAddress(), server.getLocalPort());
				//失败
				if(!Reply.isPositiveCompletion(replyCode)) {
					return null;
				}
				//重置偏移量
				if ((restartOffset > 0) && !restart(restartOffset)) {
					return null;
				}
				//发送命令
				if (!Reply.isPositivePreliminary(sendCommand(command, arg))) {
					return null;
				}
				if(dataTimeout>=0) {
					server.setSoTimeout(dataTimeout);
				}
				socket = server.accept();
				if(dataTimeout>=0) {
					socket.setSoTimeout(dataTimeout);
				}
				//数据缓存大小
				if(receiveDataSocketBufferSize > 0) {
					socket.setReceiveBufferSize(receiveDataSocketBufferSize);
				}
				if(sendDataSocketBufferSize > 0) {
					socket.setSendBufferSize(sendDataSocketBufferSize);
				}
			}finally {
				server.close();
			}
		}//本地被动模式
		else {
			// 使用EPSV
			boolean attemptEPSV = isUseEPSVwithIPv4() || ipv6;
			//发送被动模式命令
			if (attemptEPSV && sendCommand(FTPCmd.PASSIVE_EXTEND) == FTPReply.ENTERING_EPSV_MODE) {
				parseExtendedPassiveModeReply(replyLines.get(0));
			}else {
				if(ipv6) return null;
				//进入被动模式
				if(sendCommand(FTPCmd.PASSIVE) != FTPReply.ENTERING_PASSIVE_MODE) {
					return null;
				}
				parsePassiveModeReply(replyLines.get(0));
			}
			//创建Socket连接（数据）
			socket = socketFactory.createSocket();
			//设置缓冲大小
			if(receiveDataSocketBufferSize > 0) {
				socket.setReceiveBufferSize(receiveDataSocketBufferSize);
			}
			if(sendDataSocketBufferSize > 0) {
				socket.setSendBufferSize(sendDataSocketBufferSize);
			}
			if(passiveLocalHost != null) {
				socket.bind(new InetSocketAddress(passiveLocalHost, 0));
			}
			//数据超时时间
			if(dataTimeout>=0) {
				socket.setSoTimeout(dataTimeout);
			}
			//连接
			socket.connect(new InetSocketAddress(passiveHost, passivePort), connectTimeout);
			//重置连接
			if(restartOffset > 0 && !restart(restartOffset)) {
				socket.close();return null;
			}
			if(!Reply.isPositivePreliminary(sendCommand(command, arg))) {
				socket.close();return null;
			}
		}
		return socket;
	}
	
	/**
	 * 解析IPV6的被动模式响应
	 * @param reply 待解析的响应字符
	 */
	protected void parseExtendedPassiveModeReply(String reply) throws IOException {
		//截取字符串
		reply = reply.substring(reply.indexOf('(') + 1, reply.indexOf(')')).trim();
		char delim1 = reply.charAt(0),
			 delim2 = reply.charAt(1),
			 delim3 = reply.charAt(2),
			 delim4 = reply.charAt(reply.length()-1);
		//判断格式
		if(delim1!=delim2 || delim2!=delim3 || delim3!=delim4) {
			throw new IOException("不能解析被动模式扩展主机信息。\n服务响应：" + reply);
		}
		try {
			//获取端口和主机
			passivePort = Integer.parseInt(reply.substring(3, reply.length()-1));
			passiveHost = getRemoteAddress().getHostAddress();
		}catch(NumberFormatException e) {
			throw new IOException("不能解析被动模式扩展主机信息。\n服务响应：" + reply);
		}
	}
	
	/**
	 * 解析被动模式响应
	 * @param reply 待解析的响应字符
	 */
	protected void parsePassiveModeReply(String reply) throws IOException {
		Matcher m = PASV_RESP_PATTERN.matcher(reply);
		if(!m.find()) {
			throw new IOException("不能解析被动模式主机信息。\n服务响应：" + reply);
		}
		//被动主机
		passiveHost = m.group(1).replace(',', '.');
		try {
			int oct1 = Integer.parseInt(m.group(2));
			int oct2 = Integer.parseInt(m.group(3));
			passivePort = (oct1 << 8) | oct2;
		}catch (NumberFormatException e) {
			throw new IOException("不能解析被动模式主机信息。\n服务响应：" + reply);
		}
		//被动模式下的内部IP替换
		if(passiveNatWorkaround) {
			try {
				InetAddress host = InetAddress.getByName(passiveHost);
				if (host.isSiteLocalAddress()) {
					InetAddress remote = getRemoteAddress();
					if (!remote.isSiteLocalAddress()){
						String hostAddress = remote.getHostAddress();
						fireReplyReceived(0, "[Replacing site local address "+passiveHost+" with "+hostAddress+"]\n");
						passiveHost = hostAddress;
					}
				}
			}catch(UnknownHostException e) {
				throw new IOException("不能解析被动模式主机信息。\n服务响应：" + reply);
			}
		}
	}
	
	/**
	 * 改变目录
	 * @param pathname 目录
	 * @param makeDiretory 如果目录不存在，是否创建目录？如果不创建，则抛出异常
	 */
	private boolean changeWorkingDirectory(String pathname, boolean makeDiretory) throws IOException {
		//替换斜线
		pathname = pathname.replaceAll("\\\\", "/");
		//开始
		if(pathname.startsWith("/")){
			//回到根目录
			this.changeToRootDirectory();
			//去掉开头的“/”
			pathname = pathname.substring(1);
		}
		//分割目录
		String[] paths = pathname.split("/");
		//判断..回退
		boolean startDot = false;
		//循环
		for(int i=0,len=paths.length;i<len;i++){
			//当前目录名
			String path = paths[i];
			//开始
			if(i==0 && "..".equals(path)){
				startDot = true;
			}
			if(startDot && "..".equals(path)){
				this.changeToParentDirectory();
			}
			//不存在，抛出异常
			else if(!this.exist(path)){
				//目录不存在
				if(makeDiretory){//若创建目录，则不抛出异常
					this.makeDirectorys(path);
				}else{
					throw new FileNotFoundException("目录不存在");
				}
			}else{
				//不是以点开始
				startDot = false;
				//改变目录
				this.changeWorkingDirectory(path);
			}
		}
		//操作正常，返回true
		return true;
	}
	
	private OutputStream getBufferedOutputStream(OutputStream outputStream) {
		if (bufferSize > 0) {
			return new BufferedOutputStream(outputStream, bufferSize);
		}
		return new BufferedOutputStream(outputStream);
	}

	private InputStream getBufferedInputStream(InputStream inputStream) {
		if (bufferSize > 0) {
			return new BufferedInputStream(inputStream, bufferSize);
		}
		return new BufferedInputStream(inputStream);
	}
}
